package q7_gc_test;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/*
请谈谈你对OOM的认识？
java.lang.Object
  继承者 java.lang.Throwable
      继承者 java.lang.Error
          继承者 java.lang.VirtualMachineError
              继承者 java.lang.StackOverflowError
java.lang.Object
  继承者 java.lang.Throwable
      继承者 java.lang.Error
          继承者 java.lang.VirtualMachineError
              继承者 java.lang.OutOfMemoryError

1、java.lang.StackOverflowError

2、java.lang.OutOfMemoryError: Java heap space

3、java.lang.OutOfMemoryError: GC overhead limit exceeded

4、java.lang.OutOfMemoryError: Direct buffer memory

5、java.lang.OutOfMemoryError: unable to create new native thread

6、java.lang.OutOfMemoryError: Metaspace

 */
public class OOMDemo {

    public static void main(String[] args) {
        //stackOverflowError();

        //javaHeapSpace();

        //gcOverheadLimitExceeded();

        //directBufferMemory();

        //unableCreateNewNativeThread();

        metaspaceOutOfMemory(args);
    }

    //java.lang.StackOverflowError
    public static void stackOverflowError() {
        stackOverflowError();
    }

    //JVM参数配置演示：-Xms10m -Xmx10m
    //java.lang.OutOfMemoryError: Java heap space
    public static void javaHeapSpaceDemo() {
        byte[] bytes = new byte[80 * 1024 * 1024];
    }

    /*
    JVM参数配置演示
    -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails

    GC回收时间过长时会抛出java.lang.OutOfMemoryError: GC overhead limit exceeded。
    过长的定义是，超过98%的时间用来做GC并且回收了不到2%的堆内存，连续多次GC，都只回收了不到2%的极端情况下才会抛出。
    假如不抛出GC overhead limit exceeded 错误会发生什么情况呢？
    CPU使用率一直是100%，而GC却没有任何成果。
     */
    public static void gcOverheadLimitExceeded() {
        int i = 0;
        List<String> list = new ArrayList<>();

        try {
            while (true) {
                list.add(String.valueOf(++i).intern());
            }
        } catch (Exception e) {
            System.out.println("########i:" + i);
            e.printStackTrace();
            throw e;
        }
    }

    /*
    JVM参数配置演示
    -Xms10m -Xmx10m -XX:MaxDirectMemorySize=5m -XX:+PrintGCDetails

    故障现象：
    Exception in thread "main" java.lang.OutOfMemoryError: Direct buffer memory

    导致原因：
    写NIO程序经常使用ByteBuffer来读取或者写入数据，这是一种基于通道（Channel）和缓冲区（Buffer）的IO方式，
    它可以使用native函数库直接分配堆外内存，然后通过一个存储在java堆里面的DirectByteBuffer对象作为这块内存的引用进行操作。
    这样能在一些场景中显著提高性能，因为避免了在java堆和native堆中来回复制数据。

    ByteBuffer.allocate(capability) 第一种方式是分配JVM堆内存，属于GC管辖范围，由于需要拷贝所以速度相对较慢。
    ByteBuffer.allocteDirect(capability) 第一种方式是分配OS本地内存，不属于GC管辖范围，由于不需要内存拷贝所以速度相对较快。

    但如果不断分配本地内存，堆内存很少使用，那么JVM就不需要执行GC，DirectByteBuffer对象们就不会被回收，
    这时候堆内存充足，但本地内存可能已经使用光了，再次尝试分配本地内存就会出现OutOfMemoryError，那程序就直接崩溃了。
     */
    public static void directBufferMemory() {

        //默认为物理内存的1/4
        System.out.println("配置的maxDirectMemory:" + (sun.misc.VM.maxDirectMemory() / (double) 1024 / 1024) + "MB");

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //-XX:MaxDirectMemorySize=5m 我们配置为5MB，但实际使用6MB，故意使坏
        ByteBuffer bb = ByteBuffer.allocateDirect(6 * 1024 * 1024);
    }

    /*
    高并发请求服务器时，经常出现如下异常：java.lang.OutOfMemoryError: unable to create new native thread
    准确的讲该native thread异常与对应的平台有关

    导致原因：
    1、你的应用创建了太多线程了，一个应用进程创建多个线程，超过系统承载权限
    2、你的服务器并不允许你的应用程序创建这么多线程，linux系统默认允许单个进程可以创建的线程数是1024个，
       你的应用创建超过这个数量，就会报java.lang.OutOfMemoryError: unable to create new native thread

    解决办法：
    1、想办法降低你应用程序创建线程的数量，分析应用是否真的需要创建这么多线程，如果不是，改代码将线程数降到最低
    2、对于有的应用，确实需要创建很多线程，远超过linux系统的默认1024个线程的限制，可以通过修改linux服务器配置，扩大linux默认限制。

    注意：不能用root用户执行程序，默认没有限制，普通用户默认1024个线程的限制
     */
    public static void unableCreateNewNativeThread() {
        for(int i =1; ; i++){
            System.out.println("********* i = "+ i);
            new Thread(()->{
                try {
                    Thread.sleep(Integer.MAX_VALUE);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            },"" + i).start();
        }
    }

    /*
    java -XX:+PrintFlagsFinal
    可以查看元空间的大小默认约为20.8M

    JVM 参数
    -XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m

    Java8及以后的版本使用Metaspace来替代永久代

    Metaspace是方法区在HotSpot中的实现，它与永久代最大的区别大于：Metaspace并不在虚拟机内存中而是使用本地内存
    也即在java8中，class metadata(the virtual machines internal presentation of Java class)，被存储在叫做Metaspace的native memory

    永久代（java8后被元空间Metaspace替代了）存放了以下信息：
        虚拟机加载的类信息
        常量池
        静态变量
        即时编译后的代码

    模拟Metaspace空间溢出，我们不断生成类往元空间灌，类占据的空间总是会超过Metaspace指定的空间大小的
     */
    public static void metaspaceOutOfMemory(String[] args){
        int i = 0;
        try {
            while(true){
                i++;
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(OOMTest.class);
                enhancer.setUseCache(false);
                enhancer.setCallback(new MethodInterceptor(){
                    @Override
                    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        return methodProxy.invokeSuper(o, args);
                    }
                });
                enhancer.create();
            }
        }catch (Throwable e){
            System.out.println("**********多少次后发生异常：" + i);
            e.printStackTrace();
        }
    }

    static class OOMTest{}
}
